package EncryptionAndSecurity;

import org.junit.Test;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
import java.util.Base64;

/**
 * utf编码,一个中文三个字节 %E4%B8%AD%
 */
public class EncoderUrl {
    public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        String encoded = URLEncoder.encode("中文!", "ASCII");
        String encoded2 = URLEncoder.encode("中文!", "Unicode");
        String encoded3 = URLEncoder.encode("中文!", "utf-8");
        System.out.println(encoded);
        System.out.println(encoded2);
        System.out.println(encoded3);
      /*  byte[] input = new byte[] { (byte) 0xe4, (byte) 0xb8, (byte) 0xad };
        String b64encoded = Base64.getEncoder().encodeToString(input);
        System.out.println(b64encoded);*/

       /* byte[] input = new byte[] { (byte) 0xe4, (byte) 0xb8, (byte) 0xad, 0x21 };
        String b64encoded = Base64.getEncoder().encodeToString(input);
        String b64encoded2 = Base64.getEncoder().withoutPadding().encodeToString(input);
        System.out.println(b64encoded);
        System.out.println(b64encoded2);
        byte[] output = Base64.getDecoder().decode(b64encoded2);
        System.out.println(Arrays.toString(output));*/
        byte[] input = new byte[]{0x01, 0x02, 0x7f, 0x00};
        String b64encoded = Base64.getUrlEncoder().encodeToString(input);
        System.out.println(b64encoded);
        byte[] output = Base64.getUrlDecoder().decode(b64encoded);
        System.out.println(Arrays.toString(output));
        // 创建一个MessageDigest实例:
        MessageDigest md = MessageDigest.getInstance("MD5");
        MessageDigest md1 =MessageDigest.getInstance("MD5");
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        // 反复调用update输入数据:
        md.update("Hello".getBytes("UTF-8"));
        md.update("World".getBytes("UTF-8"));
        md1.update("HelloWorld".getBytes("UTF-8"));
        System.out.println("********");

        System.out.println(new BigInteger(1, md1.digest()).toString(16));
        byte[] result = md.digest(); // 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6
        System.out.println(new BigInteger(1, result).toString(16));

        try {
            KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
            SecretKey key = keyGen.generateKey();
            // 打印随机生成的key:
            byte[] skey = key.getEncoded();
            System.out.println(new BigInteger(1, skey).toString(16));
            System.out.println(new BigInteger(1, skey).toString(16).length());
            Mac mac = Mac.getInstance("HmacMD5");
            mac.init(key);
            mac.update("HelloWorld".getBytes("UTF-8"));
            byte[] result1 = mac.doFinal();
            System.out.println(new BigInteger(1, result1).toString(16));
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void Hmac() {
        byte[] hkey = new byte[]{106, 70, -110, 125, 39, -20, 52, 56, 85, 9, -19, -72, 52, -53, 52, -45, -6, 119, -63,
                30, 20, -83, -28, 77, 98, 109, -32, -76, 121, -106, 0, -74, -107, -114, -45, 104, -104, -8, 2, 121, 6,
                97, -18, -13, -63, -30, -125, -103, -80, -46, 113, -14, 68, 32, -46, 101, -116, -104, -81, -108, 122,
                89, -106, -109};

        try {
            SecretKey key = new SecretKeySpec(hkey, "HmacMD5");
            Mac mac = Mac.getInstance("HmacMD5");
            mac.init(key);
            mac.update("HelloWorld".getBytes("UTF-8"));
            byte[] result = mac.doFinal();
            System.out.println(Arrays.toString(result));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // [126, 59, 37, 63, 73, 90, 111, -96, -77, 15, 82, -74, 122, -55, -67, 54]
    }

    @Test
    public void AES() {
        // 原文:
        try {
            String message = "Hello, world!";
            System.out.println("Message: " + message);
            // 128位密钥 = 16 bytes Key:
            byte[] key = "1234567890abcdef".getBytes("UTF-8");
            // 加密:
            byte[] data = message.getBytes("UTF-8");
            byte[] encrypted = ECBencrypt(key, data);
            System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
            // 解密:
            byte[] decrypted = ECBdecrypt(key, encrypted);
            System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 加密:
    public static byte[] ECBencrypt(byte[] key, byte[] input) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKey keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        return cipher.doFinal(input);
    }

    // 解密:
    public static byte[] ECBdecrypt(byte[] key, byte[] input) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKey keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        return cipher.doFinal(input);
    }

    @Test
    public void CBC() {

        // 原文:
        String message = "Hello, world!";
        try {
            System.out.println("Message: " + message);
            // 256位密钥 = 32 bytes Key:
            byte[] key = "1234567890abcdef1234567890abcdef".getBytes("UTF-8");

            // 加密:
            byte[] data = message.getBytes("UTF-8");
            byte[] encrypted = encrypt(key, data);
            System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
            // 解密:
            byte[] decrypted = decrypt(key, encrypted);
            System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 加密:
    public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        // CBC模式需要生成一个16 bytes的initialization vector:
        SecureRandom sr = SecureRandom.getInstanceStrong();
        byte[] iv = sr.generateSeed(10);
        IvParameterSpec ivps = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
        byte[] data = cipher.doFinal(input);
        // IV不需要保密，把IV和密文一起返回:
        return join(iv, data);
    }

    // 解密:
    public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
        // 把input分割成IV和密文:
        byte[] iv = new byte[16];
        byte[] data = new byte[input.length - 16];
        System.arraycopy(input, 0, iv, 0, 16);
        System.arraycopy(input, 16, data, 0, data.length);
        // 解密:
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        IvParameterSpec ivps = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
        return cipher.doFinal(data);
    }

    public static byte[] join(byte[] bs1, byte[] bs2) {
        byte[] r = new byte[bs1.length + bs2.length];
        System.arraycopy(bs1, 0, r, 0, bs1.length);
        System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
        return r;
    }
    static class PBE {
        public static void main(String[] args) {

            // 把BouncyCastle作为Provider添加到java.security:
            try {
                // Security.addProvider(new BouncyCastleProvider());
                // 原文:
                String message = "Hello, world!";
                // 加密口令:
                String password = "hello12345";
                // 16 bytes随机Salt:
                byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16);
                System.out.printf("salt: %032x\n", new BigInteger(1, salt));
                // 加密:
                byte[] data = message.getBytes("UTF-8");
                byte[] encrypted = encrypt(password, salt, data);
                System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
                // 解密:
                byte[] decrypted = decrypt(password, salt, encrypted);
                System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (GeneralSecurityException e) {
                e.printStackTrace();
            }
        }

        // 加密:
        public static byte[] encrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException {
            PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
            SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
            SecretKey skey = skeyFactory.generateSecret(keySpec);
            PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000); //循环次数
            Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
            cipher.init(Cipher.ENCRYPT_MODE, skey, pbeps);
            return cipher.doFinal(input);
        }

        // 解密:
        public static byte[] decrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException {
            PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
            SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
            SecretKey skey = skeyFactory.generateSecret(keySpec);
            PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
            Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
            cipher.init(Cipher.DECRYPT_MODE, skey, pbeps);
            return cipher.doFinal(input);
        }
    }
}
